Release 10.1A: OpenEdge Development:
Progress 4GL Handbook
Super procedure guidelines
There is nothing specific that identifies a Progress procedure file as a super procedure. However, some guidelines can help in using super procedures effectively and without confusion.
The best general statement is that it is a good practice to consider some procedure files to be application objects, which implement specific application behavior, and others to be service procedures that operate in the background to provide standard behavior for a whole class of application objects. These latter procedures should be your super procedures. Said another way, a super procedure should be a library of standard behavior that many individual application objects can use. What does this mean in practice?
Guideline 1: Use a single super procedure stack
Super procedures should generally not have a super procedure stack of their own. The stack of super procedures (if there is more than one) for an application object should be defined by the object procedure itself. This is done by always using the
SEARCH-TARGETkeyword in theADD-SUPER-PROCEDUREstatements. This gives you maximum flexibility to define and modify the stack as needed without the individual super procedures having to be aware of each other.An example can help clarify how this works. In this example, you have an application object procedure and two super procedures from which it inherits standard behavior, as illustrated in Figure 14–2.
Figure 14–2: An object procedure and two super procedures
![]()
This is accomplished by running all the procedures persistent and then executing these statements from the object procedure:
The object procedure executes the following statement:
The Progress interpreter searches for an internal procedure named
startMeUpfirst in the object procedure itself, then in the code of the last super procedure added (h2nd), and finally in the code of the first super procedure added (hTop). There is an implementation of this internal procedure in all three places and they are all intended to run in sequence. The code in the top super procedure is the most general and is executed last. The code in the 2nd super procedure is more specific and is executed second. And the code in the object procedure itself is specific to that particular object and is executed first. Figure 14–3 shows the sequence of control that takes place at run time.Figure 14–3: Super procedure stack execution
![]()
The code executes as follows:
- The interpreter locates the implementation of
startMeUpin the object procedure instance and runs it. ThestartMeUpprocedure executes its application-specific code. TheRUN SUPERstatement causes the interpreter to search up the super procedure stack for another version of the same internal procedure name.- The interpreter searches the instance of the 2nd super procedure.
- The interpreter finds and executes the version of
startMeUpin the 2nd super procedure. It runs some code and then does anotherRUN SUPER. Here is where the use ofSEARCH-TARGETbecomes significant. If you didn’t specify this keyword on theADD-SUPER-PROCEDUREmethod, the default would beSEARCH-SELF, which means that theRUN SUPERstatement in the 2nd super procedure would cause the interpreter to search up the 2nd super procedure’s own super procedure stack for another version ofstartMeUp. Because the 2nd super procedure has no super procedures of its own, the interpreter would find nothing and theRUN SUPERstatement would return an error.- Instead, the use of
SEARCH-TARGETcauses the interpreter to go back to the object procedure wherestartMeUpwas run in the first place and continue to search up its stack. This causes it to search the top super procedure.- Finally, the interpreter locates and executes the top-most version of
startMeUp.Now compare this with what is required if you do not use
SEARCH-TARGET. For theRUN SUPERstatement in the 2nd super procedure to execute properly, you need a double set of super procedure definitions, with this statement in the initialization code for the second super procedure:
Figure 14–4 diagrams this code.
Figure 14–4: Super procedure stack execution without SEARCH-TARGET
![]()
Now the
RUN SUPERstatement in the 2nd super procedure causes the interpreter to search up its own stack (4), finding the top-level code to execute (5).So what’s the problem with this approach? This potentially complicates the application considerably. Each super procedure must have its own initialization code to establish its own procedure stack, which must duplicate that portion of the object procedure’s stack above itself (somewhat akin to the old song The Twelve Days of Christmas, each super procedure moving down the chain from the top must duplicate the chain from itself on up). This is not only more work than the other approach, but it also makes it more difficult to build any flexibility into the scheme. What if some object procedure wants to have a different procedure stack? This isn’t really possible because the super procedures themselves duplicate the stack and therefore make it almost impossible to change from object procedure to object procedure. Or what if an object procedure wants to insert an additional super procedure into the stack conditionally? This also can’t really be done. Simply put, unless there are specific circumstances dictating otherwise, it is the best practice always to use the
SEARCH-TARGETkeyword.Guideline 2: Use TARGET-PROCEDURE to refer back to the object procedure
If code in a super procedure is to be general-purpose, so that it can serve any object procedure needing it, it must always refer back to the object procedure for handles or other values it needs to make calculations or execute code on behalf of the object. There are two ways you can do this:
- You can pass any needed values into the procedure call as input parameters. This is a simple technique but not necessarily an effective one. For one thing, putting values into parameters hard-codes the list of needed values forevermore. This can lead to serious maintainability problems. Keep in mind that not only every reference to the internal procedure or function, but also every
RUN SUPERstatement inside it, must duplicate the same parameter list. Any change to that list will be a maintenance nightmare. Also, specifying the needed parameters violates the object-oriented nature of the relationship between the two procedures in that it is forcing the object procedure to know something about the super procedure’s implementation of the internal procedure, namely what information it needs to operate on.Beyond this, it is often awkward to specify parameters for another reason. The object procedure itself probably doesn’t need the information itself—it’s presumably going to be available to it somehow, through local variable names or whatever else—so the parameter won’t be used by a localization of the routine in the object procedure, if there is one. The parameter is only needed when the local procedure passes control to a separately compiled procedure that doesn’t have access to the local variable. This makes use of the parameter look awkward.
- A better and more object-oriented technique is to allow the super procedure to refer back to values that the object procedure makes available. This technique provides more independence between the user of the standard behavior and the implementation of that behavior. The traditional Progress programming technique of using
SHAREDvariables doesn’t work in this case, because there is no top-down hierarchy of procedures to let Progress make those values available. The alternative is to allow the super procedure to refer back into the object procedure in some other way.There are two techniques that illustrate ways to do this, both using the
TARGET-PROCEDUREbuilt-in function.When an object procedure runs a routine such as
startMeUpand the interpreter locates it in a super procedure, then within thestartMeUpcode inside the super procedure theTARGET-PROCEDUREfunction evaluates to the procedure handle of the object procedure where the routine was originally run. Alternatively, if there is a local implementation ofstartMeUpin the object procedure, which then executes aRUN SUPERstatement, then the super procedure code can likewise useTARGET-PROCEDUREto obtain the procedure handle of the original object.Given this handle, the super procedure code can do one of two kinds of things:
- It can run a procedure or function
IN TARGET-PROCEDUREthat returns the desired value. You can do this by defining functions with the namegetproperty, where thepropertyname is the logical name of the value needed. For every piece of public data that an object makes available to other procedures, the procedure must define such a function. Alternatively, you can define a single function that takes the name of the property as an input parameter and returns its value. Within the limitations of what the object determines to be public data, any other procedure can retrieve needed values at will.- A second kind of technique is to make the values available through the object’s procedure handle. For example, you can store the handle of a temp-table buffer containing all of a SmartObject’s properties in the
ADM-DATAattribute of the object’s procedure handle. User application code can use the similarPRIVATE-DATAattribute to store this kind of information as well: either a list of the values themselves, in some delimited form, or a handle of a buffer where they are stored.Keep in mind that it is the nature of how handles are used in the Progress language that once a procedure object in a given OpenEdge session has the handle of something defined in another procedure instance, it can operate on that handle exactly as the other procedure can. This rule applies to handles of visual controls such as fields, browses, buttons, dynamic buffer handles, dynamic query handles, and dynamic temp-table handles. This is part of what makes the super procedure mechanism so powerful, though it compromises the object-oriented nature of the procedures as well-isolated individual objects. Once you let another procedure gain access to your procedure by giving away, say, the frame handle for the frame your procedure defines, everything else related to that is freely available.
Another important point to keep in mind about
TARGET-PROCEDUREis that Progress re-evaluates it every time a new procedure or function name is invoked. For example, throughout the little example in this section, the value ofTARGET-PROCEDUREinside any version ofstartMeUpis the procedure handle of the procedure in whichstartMeUpwas originally invoked (that is, the handle of the procedure where the originalRUNstatement was located or, if that originalRUNstatement wasRUN startMeUp INsome-other-proc-hdl, then the value ofsome-other-proc-hdl). This is true no matter how many nested levels ofRUN SUPERstatements you go through. However, as soon as some other routine is run, the value ofTARGET-PROCEDUREchanges to be the procedure handle where that routine was run. Once any and all versions of that new routine execute and control returns to some version ofstartMeUp, then the value ofTARGET-PROCEDUREpops back to what it was before.For this reason, it is important for super procedures to invoke other routines
IN TARGET-PROCEDUREif there is any chance that the newly run routine needs to refer toTARGET-PROCEDUREitself. For example, if thestartMeUpcode in the 2nd super procedure needs to run another internal procedure calledmoreStartupStuff, then even ifmoreStartupStuffis also implemented in the 2nd super procedure, you should invoke it by using theRUN moreStartupStuff IN TARGET-PROCEDUREstatement if it needs to refer toTARGET-PROCEDUREitself (for example, to retrieve another property value from the object procedure). If you don’t do this, then the value ofTARGET-PROCEDUREinsidemoreStartupStuffbecomes theh2ndsuper procedure handle, which is not useful. This statement also lets the object procedure localizemoreStartupStuffif it needs to. If you don’t desire this behavior (that is, if you want this subprocedure to be invisible to object procedures), then you should definemoreStartupStuffasPRIVATE, and then pass the value ofTARGET-PROCEDUREinto it as a parameter if it’s going to be needed.Guideline 3: Make super procedure code shareable
If you write a super procedure as a library of general-purpose code, it makes sense that in most cases it should be shareable. The use of
TARGET-PROCEDUREfacilitates this. If the routines in a super procedure always refer back to theTARGET-PROCEDURE, then they always get data values from the procedure they currently support, that is, the procedure they were invoked from. To make sure that stale data isn’t left over from call to call, the general rule is to have no variables or any other definitions scoped to the super procedure main block. You should place all definitions within each individual internal procedure or function, so that they safely go out of scope when the routine exits.This also means that you should structure your application so that only one instance of each super procedure starts for a session. The following example uses the simple mechanism of checking existing procedure handles and their filenames to see if the super procedure is already running, as shown in this procedure, used by all SmartObjects:
Guideline 4: Avoid defining object properties and events in super procedures
This guideline is related to the previous ones about always referring back to
TARGET-PROCEDUREfor any data needed for an operation done by a super procedure. It also relates to keeping all super procedure data local to the individual internal procedure or function. You should define a property or other persistent data value in a super procedure only if it is truly global. (That is, if it is to be shared by all object procedures that use that super procedure.) This should definitely be the exception.Similarly, it is a good rule that super procedures should not subscribe to or publish named events directly. You learn about the Progress
PUBLISHandSUBSCRIBEsyntax in the "PUBLISH and SUBSCRIBE statements" section. APUBLISHorSUBSCRIBEstatement should always be done on behalf of the object procedures they serve. Super procedure code might need to subscribe object procedures to named events to set up relationships between procedures.Forgetting to carry out program actions relative to
TARGET-PROCEDUREis one of the most common mistakes in using super procedures.Guideline 5: Never run anything directly in a super procedure
As discussed earlier, the whole mechanism of using
TARGET-PROCEDUREas a means to identify the handle of the object on whose behalf an action is being taken depends on the routine that executes the action being invokedINthe object procedure. The Progress interpreter then takes care of the work of locating the routine in a super procedure and executing it there. This works properly only if application code never runs any routines directly in a super procedure handle. The example shown in Figure 14–5 helps to illustrate this point.Figure 14–5: Running a procedure within a super procedure
![]()
Here’s the (undesired) sequence of events:
- Initialization code in the object procedure runs
startMeUp, but runs it explicitlyINthe handle of its super procedure, rather than letting the interpreter do this implicitly.- Progress does what it’s told and runs
startMeUp IN hSuper.- Because
startMeUpwas specifically runIN hSuper, thenhSuperbecomes theTARGET-PROCEDURE, the handle of the procedure in which the routine was originally invoked.- Thus, any reference to
TARGET-PROCEDUREcauses the interpreter to search the super procedure itself for the routine to execute.- Progress never finds and executes the version of
nowDoThisback in the object itself.For the super procedure to get back to the object procedure, it needs to use the
SOURCE-PROCEDUREbuilt-in function, but even this won’t always work if the originalRUNstatement is located anywhere except in the actual source code for the object procedure. So, the whole relationship between the object procedure and its supporting super procedure becomes messed up, and you can expect confusing results.Note that even in a case where you are defining a global property in a super procedure, as discussed in the previous section, an object that wants to get at that property value should still do it indirectly, by requesting it from itself, and letting the interpreter locate the routine that supplies the value in the super procedure. Figure 14–6 shows an example.
Figure 14–6: A global property in a super procedure
![]()
The sequence of events in Figure 14–6 executes as follows:
- The main block of the super procedure defines the value to return to any requesting object procedure, outside the scope of an internal procedure or function. This is not actually a Progress
GLOBALvariable (which you should avoid), but because it is effectively global to all procedures using this super procedure, in the sense that its value is persistent across calls from many objects, you can use a naming convention of preceding the name with agto make it clear that it’s defined at the scope of the whole super procedure.- An object procedure requests the value by executing a function that’s defined in the super procedure. But the function is invoked in the object procedure itself, not directly in the handle of the super procedure. The interpreter locates the function in the super procedure and executes it there. Invoking the function directly in the super procedure by using the syntax
DYNAMIC-FUNCTION (‘valueEverybodyNeeds’ IN hSuper)is a bad idea. First, because it messes up any attempt by the function code to referenceTARGET-PROCEDURE, and, second, because it hard-codes into the object procedure the fact that the supporting function is implemented in this particular super procedure. This makes the application more difficult to maintain.- The supporting function returns the value, which is then used by the object procedure.
This simple example presumes that there is a function prototype defined in the object procedure so that the compiler knows how to evaluate the function. You can accomplish this easily by using the ProtoGen tool in the PRO*Tools palette of the AppBuilder, which generates a Progress include file containing a prototype for every routine defined in the super procedure. Your code can then include this file in the object procedure to make the prototypes available.
|
Copyright © 2005 Progress Software Corporation www.progress.com Voice: (781) 280-4000 Fax: (781) 280-4095 |